/**
 * @file exercise.c
 *
 */

/*********************
*      INCLUDES
*********************/
#include "exercise.h"

#if USE_LV_WATCH_EXERCISE != 0

/*********************
*      DEFINES
*********************/
#define EXERCISE_ROLLER_VISIBLE_ROW_COUNT              (3)
                                                       
#define EXERCISE_EFFICIENT_FAT_BURNING_HR_LOW_VALUE    (107)
#define EXERCISE_EFFICIENT_FAT_BURNING_HR_HIGH_VALUE   (134)
                                                       
#define EXERCISE_HEART_LUNG_IMPROVE_HR_LOW_VALUE       (116)
#define EXERCISE_HEART_LUNG_IMPROVE_HR_HIGH_VALUE      (160)
                                                       
#define EXERCISE_RECOVERY_TRAINING_HR_LOW_VALUE        (116)
#define EXERCISE_RECOVERY_TRAINING_HR_HIGH_VALUE       (148)
                                                       
#define EXERCISE_CUSTOM_HR_STEP                        (5)
                                                       
#define EXERCISE_MIN_HR_HIGH_VALUE                     (70)
#define EXERCISE_MAX_HR_HIGH_VALUE                     (220)
#define EXERCISE_DEF_HR_HIGH_VALUE                     (165)
                                                       
#define EXERCISE_MIN_HR_LOW_VALUE                      (35)
#define EXERCISE_MAX_HR_LOW_VALUE_BELOW_HIGH_VALUE     (20)
#define EXERCISE_DEF_HR_LOW_VALUE                      (EXERCISE_MIN_HR_HIGH_VALUE -               \
                                                        EXERCISE_MAX_HR_LOW_VALUE_BELOW_HIGH_VALUE)

#define EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM (4)

#define EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE        (390) // in sec / km
#define EXERCISE_GOAL_PACE_EXPERT_TIME_VALUE         (300) // in sec / km
#define EXERCISE_GOAL_PACE_CUSTOM_MIN_TIME_VALUE     (120) // in sec / km
#define EXERCISE_GOAL_PACE_CUSTOM_MAX_TIME_VALUE     (900) // in sec / km
#define EXERCISE_GOAL_PACE_CUSTOM_DEF_TIME_VALUE     (325) // in sec / km
#define EXERCISE_GOAL_PACE_CUSTOM_TIME_STEP          (5)   // in sec / km

/**********************
*      TYPEDEFS
**********************/

/**********************
*  STATIC PROTOTYPES
**********************/
typedef struct {
    lv_watch_obj_ext_t old_ext;
    lv_obj_t * par_btn;
    exercise_item_t * item;
} exercise_setting_ext_t;

typedef struct
{
    lv_obj_t * par;
    char * opt;
    uint8_t visible_row_count;
    uint16_t sel_id;
    uint16_t title_txt_id;
    uint16_t btn_txt_id;
    lv_color_t color; // for btn label and roller selected option
    lv_event_cb_t roller_cb;
    lv_event_cb_t btn_cb;
} exercise_roller_ui_desc_t;

/**********************
*  STATIC VARIABLES
**********************/

/**********************
*      MACROS
**********************/

/**********************
 *   STATIC FUNCTIONS
 **********************/
static lv_obj_t * exercise_add_roller_ui(exercise_roller_ui_desc_t * desc)
{
    lv_coord_t par_w = lv_obj_get_width(desc->par);
    lv_coord_t par_h = lv_obj_get_height(desc->par);
    lv_coord_t font_h = lv_font_get_line_height(LV_THEME_WATCH_NIGHT_FONT_NORMAL);
    lv_coord_t pad_ver = (par_h - 5 * font_h) / 6;

    lv_obj_t * bg = lv_cont_create(desc->par, NULL);
    lv_obj_set_style_local_bg_opa(bg, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_COVER);
    lv_obj_set_style_local_bg_color(bg, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
    lv_obj_set_size(bg, par_w, par_h);
    lv_obj_set_click(bg, false);
    lv_obj_align(bg, desc->par, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t * label_title = lv_label_create(bg, NULL);
    lv_obj_set_style_local_text_font(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_NORMAL);
    lv_obj_set_style_local_text_color(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                      LV_COLOR_WHITE);
    lv_obj_set_style_local_text_letter_space(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, 0);
    lv_obj_set_width(label_title, par_w - 4);
    lv_label_set_text_id(label_title, desc->title_txt_id);
    lv_obj_align(label_title, bg, LV_ALIGN_IN_TOP_MID, 0, pad_ver);

    lv_obj_t * btn = lv_btn_create(bg, NULL);
    lv_btn_set_layout(btn, LV_LAYOUT_OFF);
    lv_coord_t btn_size = 3 * par_w;
    lv_obj_set_style_local_bg_opa(btn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
    lv_obj_set_style_local_bg_color(btn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
    lv_obj_set_style_local_radius(btn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
    lv_obj_set_size(btn, btn_size, btn_size);
    lv_obj_set_event_cb(btn, desc->btn_cb);
    lv_obj_align(btn, bg, LV_ALIGN_IN_BOTTOM_MID, 0, btn_size - font_h - pad_ver);
    lv_watch_obj_add_element(btn);

    lv_obj_t * label = lv_label_create(btn, label_title);
    lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, desc->color);
    lv_obj_set_width(label, par_w - 4);
    lv_label_set_text_id(label, desc->btn_txt_id);
    lv_obj_align(label, btn, LV_ALIGN_IN_TOP_MID, 0, font_h / 4);

    lv_obj_t * roller = lv_roller_create(bg, NULL);
    lv_obj_t * scrl = lv_page_get_scrollable(roller);
    lv_obj_set_drag_throw(scrl, false);
    lv_obj_set_style_local_bg_opa(roller, LV_ROLLER_PART_BG, LV_STATE_DEFAULT, LV_OPA_0);
    lv_obj_set_style_local_text_font(roller, LV_ROLLER_PART_BG, LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_NORMAL);
    lv_obj_set_style_local_text_letter_space(roller, LV_ROLLER_PART_BG, LV_STATE_DEFAULT, 0);
    lv_obj_set_style_local_text_line_space(roller, LV_ROLLER_PART_BG, LV_STATE_DEFAULT, pad_ver);
    lv_obj_set_style_local_text_color(roller, LV_ROLLER_PART_SELECTED, LV_STATE_DEFAULT,
                                      desc->color);
    lv_obj_set_style_local_text_font(roller, LV_ROLLER_PART_SELECTED, LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_BIG);
    lv_obj_set_style_local_text_letter_space(roller, LV_ROLLER_PART_SELECTED, LV_STATE_DEFAULT, 0);
    lv_roller_set_visible_row_count(roller, desc->visible_row_count);
    lv_roller_set_align(roller, LV_LABEL_ALIGN_CENTER);
    lv_roller_set_options(roller, desc->opt, LV_ROLLER_MODE_NORMAL);
    lv_roller_set_selected(roller, desc->sel_id, false);
    lv_roller_set_auto_fit(roller, false);
    lv_obj_set_width(roller, lv_obj_get_width(roller) + font_h);
    lv_obj_set_event_cb(roller, desc->roller_cb);
    lv_obj_align(roller, bg, LV_ALIGN_CENTER, 0, 0);
    lv_watch_obj_add_element(roller);
    lv_watch_obj_add_element(scrl);

    return(roller);
}

static void exercise_get_interval_reminder_txt(exercise_nv_reminder_t * nv, char * txt,
                                               uint32_t txt_len)
{
    if((NULL == nv) || (NULL == txt) || (0 == txt_len)) return;

    if(0 == nv->interval.value) {
        snprintf(txt, txt_len, "%s", (char *)lv_lang_get_text(WATCH_TEXT_ID_GOALLESSNESS));
    } else if(EXERCISE_VALUE_TYPE_DISTANCE == nv->interval.type) {
        uint8_t value = nv->interval.value / 2;
        if(0 != (nv->interval.value % 2)) {
            snprintf(txt, txt_len, "%d.5%s", value,
                     (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
        } else {
            snprintf(txt, txt_len, "%d%s", value,
                     (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
        }
    } else if(EXERCISE_VALUE_TYPE_TIME == nv->interval.type) {
        if(1 < nv->interval.value) {
            snprintf(txt, txt_len, "%d%s",
                     nv->interval.value, (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MINS));
        } else {
            snprintf(txt, txt_len, "%d%s",
                     nv->interval.value, (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MIN));
        }
    }
}

void exercise_set_roller_opt(lv_obj_t * roller, char * opt)
{
    lv_obj_t * scrl = lv_page_get_scrollable(roller);
    lv_obj_t * label = lv_obj_get_child(scrl, NULL);
    lv_label_set_text(label, opt);
    lv_obj_align(label, scrl, LV_ALIGN_IN_TOP_MID, 0, lv_obj_get_y(label));
}

static void exercise_roller_opt_ins_txt(uint16_t sel_id, char * txt, char * opt, uint32_t opt_len)
{
    if((NULL == opt) || (0 == opt_len)) {
        printf("%s: warning, opt is NULL\n", __FUNCTION__);
        return;
    }
    if(NULL == txt) {
        printf("%s: warning, txt is NULL\n", __FUNCTION__);
        return;
    }

    uint8_t txt_len = strlen(txt);
    uint8_t len = strlen(opt);
    if((len + txt_len + 1) > opt_len) {
        printf("%s: warning, opt_len %d < len %d + txt_len %d +1\n",
               __FUNCTION__, opt_len, len, txt_len);
        return;
    }
    uint8_t i = 1;
    int8_t count = -1;
    for(;i < len;i++) {
        if('\n' == opt[i]) count++;
        if(sel_id == count) break;
    }
    for(uint8_t j = len;j >= i;j--) {
        opt[j + txt_len] = opt[j];
    }
    Hal_Mem_Copy(&opt[i], txt, txt_len);
}

static uint8_t exercise_goals_get_roller_opt(EXERCISE_VALUE_TYPE_T type,
                                             uint8_t * init_sel_id, uint16_t init_value,
                                             uint16_t * value, char * opt, uint32_t opt_len)
{
    uint8_t sel_id = 0xFF;

    if(NULL == opt) {
        printf("%s: warning, opt is NULL\n", __FUNCTION__);
        return(sel_id);
    }

    if(init_sel_id) {
        if(EXERCISE_MAX_ROLLER_VALUE_NUM >= *init_sel_id) {
            sel_id = *init_sel_id;
        }
    } else {
        for(uint8_t i = 0;i < EXERCISE_MAX_ROLLER_VALUE_NUM;i++) {
            if(value[i] == init_value) {
                sel_id = i;
                break;
            }
        }
    }
    Hal_Mem_Set(opt, 0, opt_len);
    char str[5];
    for(uint8_t i = 0;i < EXERCISE_MAX_ROLLER_VALUE_NUM;i++) {
        if((EXERCISE_VALUE_TYPE_DISTANCE == type)
            && ((EXERCISE_GOALS_DISTANCE_HALF_MARATHON == value[i])
                || (EXERCISE_GOALS_DISTANCE_MARATHON == value[i]))) {
            if(EXERCISE_GOALS_DISTANCE_HALF_MARATHON == value[i]) {
                strcat(opt, (char *)lv_lang_get_text(WATCH_TEXT_ID_HALF_MARATHON));
            } else {
                strcat(opt, (char *)lv_lang_get_text(WATCH_TEXT_ID_MARATHON));
            }
        } else {
            exercise_int_to_str(value[i], str, 5);
            strcat(opt, str);
        }
        strcat(opt, "\n");
    }

    strcat(opt, (char *)lv_lang_get_text(WATCH_TEXT_ID_CUSTOM));

    if(EXERCISE_MAX_ROLLER_VALUE_NUM > sel_id) {
        if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
            if((EXERCISE_GOALS_DISTANCE_HALF_MARATHON != value[sel_id])
               && (EXERCISE_GOALS_DISTANCE_MARATHON != value[sel_id])) {
                exercise_roller_opt_ins_txt(sel_id,
                                            (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE),
                                            opt, opt_len);
            }
        } else if(EXERCISE_VALUE_TYPE_TIME == type) {
            if(1 < value[sel_id]) {
                exercise_roller_opt_ins_txt(sel_id,
                                            (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MINS),
                                            opt, opt_len);
            } else {exercise_roller_opt_ins_txt(sel_id,
                                                (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MIN),
                                                opt, opt_len);
            }
        } else if(EXERCISE_VALUE_TYPE_CALORIE == type) {
            exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_KCALORIES),
                                        opt, opt_len);
        }
    }

    return(sel_id);
}

static uint8_t exercise_roller_get_custom_opt(uint8_t visible_row_count, uint16_t sel_value,
                                              uint8_t step, char * opt, uint8_t opt_len,
                                              uint16_t min_value, uint16_t max_value, bool is_time,
                                              lv_bidi_dir_t dir)
{
    if((NULL == opt) || (0 == opt_len)) {
        printf("%s: warning, opt is NULL\n", __FUNCTION__);
        return(0xff);
    }
    if(min_value > max_value) {
        printf("%s: warning, min_vaue %d > max_value %d\n",
               __FUNCTION__, min_value, max_value);
        return(0xff);
    }
    if((min_value > sel_value) || (max_value < sel_value)) {
        printf("%s: warning, sel_vaue %d exceeds the range %d to %d\n",
               __FUNCTION__, sel_value, min_value, max_value);
        return(0xff);
    }

    Hal_Mem_Set(opt, 0, opt_len);

    uint8_t max_row_count = (max_value - min_value) / step + 1;
    uint8_t row_count = visible_row_count + 2 * (visible_row_count + 1);
    int32_t real_min_value = min_value;
    if(row_count >= max_row_count) {
        row_count = max_row_count;
    } else {
        real_min_value = sel_value - row_count / 2 * step;
        if(min_value > real_min_value) {
            real_min_value = min_value;
        } else {
            uint16_t real_max_value = sel_value + row_count / 2 * step;
            if(max_value < real_max_value) {
                real_min_value = max_value - (row_count - 1) * step;
            }
        }
    }
    uint8_t sel_id = (sel_value - real_min_value) / step;
    char str[10];
    if(is_time) {
        if(LV_BIDI_DIR_RTL == dir) {
            snprintf(str, 10, "\"%02d'%d", real_min_value % 60, real_min_value / 60);
        } else {
            snprintf(str, 10, "%d'%02d\"", real_min_value / 60, real_min_value % 60);
        }
    } else {
        exercise_int_to_str(real_min_value, str, 10);
    }
    strcat(opt, str);
    for(uint8_t i = 1;i < row_count;i++) {
        strcat(opt, "\n");
        real_min_value += step;
        if(is_time) {
            if(LV_BIDI_DIR_RTL == dir) {
                snprintf(str, 10, "\"%02d'%d", real_min_value % 60, real_min_value / 60);
            } else {
                snprintf(str, 10, "%d'%02d\"", real_min_value / 60, real_min_value % 60);
            }
        } else {
            exercise_int_to_str(real_min_value, str, 10);
        }
        strcat(opt, str);
    }

    return(sel_id);
}

static void exercise_goals_custom_roller_cb(lv_obj_t * roller, lv_event_t event)
{
    if((LV_EVENT_DRAG_BEGIN == event) || (LV_EVENT_VALUE_CHANGED == event)) {
        uint16_t sel_value;
        if(LV_EVENT_DRAG_BEGIN == event) {
            sel_value = lv_watch_get_free_num(roller);
        } else {
            char sel_str[10] = {};
            lv_roller_get_selected_str(roller, sel_str, 10);
            sel_value = exercise_str_to_int(sel_str);
            lv_watch_set_free_num(roller, sel_value);
        }
        EXERCISE_VALUE_TYPE_T type = lv_watch_get_free_num(lv_obj_get_parent(roller));
        exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
        exercise_sport_info_t sport_info;
        exercise_get_sport_info(ext->item->type, &sport_info);
        exercise_roller_desc_t roller_desc = {};
        sport_info.roller_cb(type, &roller_desc);
        char opt[100];
        uint8_t sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_value,
                                                        roller_desc.custom_step, opt, 100,
                                                        roller_desc.custom_min,
                                                        roller_desc.custom_max, false,
                                                        LV_BIDI_DIR_AUTO);

        if(LV_EVENT_VALUE_CHANGED == event) {
            if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
                exercise_roller_opt_ins_txt(sel_id,
                                            (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE),
                                            opt, 100);
            } else if(EXERCISE_VALUE_TYPE_TIME == type) {
                if(1 < sel_value) {
                    exercise_roller_opt_ins_txt(sel_id,
                                                (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MINS),
                                                opt, 100);
                } else {
                    exercise_roller_opt_ins_txt(sel_id,
                                                (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MIN),
                                                opt, 100);
                }
            } else if(EXERCISE_VALUE_TYPE_CALORIE == type) {
                exercise_roller_opt_ins_txt(sel_id,
                                            (char *)lv_lang_get_text(WATCH_TEXT_ID_KCALORIES),
                                            opt, 100);
            }
        }
        exercise_set_roller_opt(roller, opt);
        lv_roller_set_selected(roller, sel_id, false);
    }
}

static void exercise_goals_custom_ok_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * par = lv_obj_get_parent(btn);
    lv_obj_t * roller = lv_obj_get_child(par, NULL);
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_GOALS);
    exercise_nv_goals_t * nv = (exercise_nv_goals_t *)(&ext->item->data[offset]);
    uint16_t value = lv_watch_get_free_num(roller);
    EXERCISE_VALUE_TYPE_T type = lv_watch_get_free_num(par);
    if((type != nv->type) || (value != nv->value)) {
        nv->value = value;
        nv->type = type;

        exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_goals_t), nv,
                          EXERCISE_WRITE_NV_MODE_REPLACE);

        char txt[EXERCISE_MAX_TXT_LEN] = {};
        exercise_get_goals_txt(nv, txt, EXERCISE_MAX_TXT_LEN);
        lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);

        ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING);
        label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);
    }

    exercise_del_ui(ACT_ID_EXERCISE_SETTING_3);
    exercise_del_ui(ACT_ID_EXERCISE_SETTING_2);
    exercise_del_ui(ACT_ID_EXERCISE_SETTING_1);
}

static lv_obj_t * exercise_goals_custom_create(EXERCISE_VALUE_TYPE_T type)
{
    printf("%s\n", __FUNCTION__);

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_3;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    exercise_roller_desc_t roller_desc = {};
    sport_info.roller_cb(type, &roller_desc);
    uint16_t init_value = roller_desc.custom_default;

    exercise_nv_goals_t * nv
        = (exercise_nv_goals_t *)exercise_get_nv_pointer(ext->item->data,
                                                         sport_info.setting_item_bitmap,
                                                         EXERCISE_SETTING_ITEM_TYPE_GOALS);
    if((type == nv->type)
       && (roller_desc.custom_min <= nv->value)
       && (roller_desc.custom_max >= nv->value)) {
        init_value = nv->value;
    }
    char opt[100];
    uint8_t sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT, init_value,
                                                    roller_desc.custom_step, opt, 100,
                                                    roller_desc.custom_min, roller_desc.custom_max,
                                                    false, LV_BIDI_DIR_AUTO);
    if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
        exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE),
                                    opt, 100);
    } else if(EXERCISE_VALUE_TYPE_TIME == type) {
        if(1 < init_value) {
            exercise_roller_opt_ins_txt(sel_id,
                                        (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MINS),
                                        opt, 100);
        } else {
            exercise_roller_opt_ins_txt(sel_id,
                                        (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MIN),
                                        opt, 100);
        }
    } else if(EXERCISE_VALUE_TYPE_CALORIE == type) {
        exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_KCALORIES),
                                    opt, 100);
    }
    exercise_roller_ui_desc_t desc = {obj, opt, EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_id,
                                      WATCH_TEXT_ID_DISTANCE, WATCH_TEXT_ID_OK,
                                      LV_COLOR_MAKE(0, 255, 64),
                                      exercise_goals_custom_roller_cb, exercise_goals_custom_ok_cb};
    if(EXERCISE_VALUE_TYPE_TIME == type) {
        desc.title_txt_id = WATCH_TEXT_ID_TIME;
    } else if(EXERCISE_VALUE_TYPE_CALORIE == type) {
        desc.title_txt_id = WATCH_TEXT_ID_CALORIE;
        desc.color = LV_COLOR_ORANGE;
    }
    lv_obj_t * roller = exercise_add_roller_ui(&desc);
    lv_watch_set_free_num(roller, init_value);
    lv_watch_set_free_num(lv_obj_get_parent(roller), type);

    return(obj);
}

static void exercise_goals_roller_cb(lv_obj_t * roller, lv_event_t event)
{
    if((LV_EVENT_DRAG_BEGIN == event) || (LV_EVENT_VALUE_CHANGED == event)) {
        char opt[50];
        uint8_t sel_id = 0xff;
        if(LV_EVENT_VALUE_CHANGED == event) {
            sel_id = (uint8_t)lv_roller_get_selected(roller);
        }
        EXERCISE_VALUE_TYPE_T type = lv_watch_get_free_num(lv_obj_get_parent(roller));
        exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
        exercise_sport_info_t sport_info;
        exercise_get_sport_info(ext->item->type, &sport_info);
        exercise_roller_desc_t desc = {};
        sport_info.roller_cb(type, &desc);
        exercise_goals_get_roller_opt(type, &sel_id, 0, desc.value, opt, 50);
        exercise_set_roller_opt(roller, opt);
    }
}

static void exercise_goals_ok_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * par = lv_obj_get_parent(btn);
    lv_obj_t * roller = lv_obj_get_child(par, NULL);
    uint16_t index = lv_roller_get_selected(roller);
    EXERCISE_VALUE_TYPE_T type = lv_watch_get_free_num(lv_obj_get_parent(btn));
    printf("%s: selected index %d, type %d\n", __FUNCTION__, index, type);
    if(EXERCISE_MAX_ROLLER_VALUE_NUM == index) {
        //custom
        exercise_goals_custom_create(type);
    } else if(EXERCISE_MAX_ROLLER_VALUE_NUM > index) {
        exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
        exercise_sport_info_t sport_info;
        exercise_get_sport_info(ext->item->type, &sport_info);
        uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                                 EXERCISE_SETTING_ITEM_TYPE_GOALS);
        exercise_nv_goals_t * nv = (exercise_nv_goals_t *)(&ext->item->data[offset]);
        exercise_roller_desc_t roller_desc = {};
        sport_info.roller_cb(type, &roller_desc);
        uint16_t value = roller_desc.value[index];
        if((type != nv->type)
           || (value != nv->value)) {
            nv->type = type;
            nv->value = value;
            exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_goals_t), nv,
                              EXERCISE_WRITE_NV_MODE_REPLACE);

            char txt[EXERCISE_MAX_TXT_LEN] = {};
            exercise_get_goals_txt(nv, txt, EXERCISE_MAX_TXT_LEN);
            lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
            lv_label_set_text(label, txt);

            ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING);
            label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
            lv_label_set_text(label, txt);
        }
        exercise_del_ui(ACT_ID_EXERCISE_SETTING_2);
        exercise_del_ui(ACT_ID_EXERCISE_SETTING_1);
    }
}

static lv_obj_t * exercise_goals_create(EXERCISE_VALUE_TYPE_T type)
{
    printf("%s: type %d\n", __FUNCTION__, type);

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_2;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    exercise_nv_goals_t * nv
        = (exercise_nv_goals_t *)exercise_get_nv_pointer(ext->item->data,
                                                         sport_info.setting_item_bitmap,
                                                         EXERCISE_SETTING_ITEM_TYPE_GOALS);
    exercise_roller_desc_t roller_desc = {};
    sport_info.roller_cb(type, &roller_desc);
    uint16_t init_value = 0;
    if(type == nv->type) {
        init_value = nv->value;
    }
    if(0 == init_value) {
        init_value = roller_desc.value[2];
    }
    char opt[50];
    uint8_t sel_id = 0;
    sel_id = exercise_goals_get_roller_opt(type, NULL, init_value, roller_desc.value, opt, 50);
    exercise_roller_ui_desc_t desc = {obj, opt, EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_id,
                                      WATCH_TEXT_ID_DISTANCE, WATCH_TEXT_ID_OK,
                                      LV_COLOR_MAKE(0, 255, 64),
                                      exercise_goals_roller_cb, exercise_goals_ok_cb};
    if(EXERCISE_VALUE_TYPE_TIME == type) {
        desc.title_txt_id = WATCH_TEXT_ID_TIME;
    } else if(EXERCISE_VALUE_TYPE_CALORIE == type) {
        desc.title_txt_id = WATCH_TEXT_ID_CALORIE;
        desc.color = LV_COLOR_ORANGE;
    }
    lv_obj_t * roller = exercise_add_roller_ui(&desc);
    lv_watch_set_free_num(lv_obj_get_parent(roller), type);

    return(obj);
}

static void exercise_smart_coach_sw_cb(lv_obj_t * sw, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    bool state = lv_switch_get_state(sw);
    printf("%s: new state %d\n", __FUNCTION__, state);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_SMART_COACH);
    exercise_nv_smart_coach_t * nv = (exercise_nv_smart_coach_t *)(&ext->item->data[offset]);
    lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
    if(state) {
        nv->is_smart_coach_on = 1;
        lv_label_set_text(label, (char *)lv_lang_get_text(WATCH_TEXT_ID_ON));
    } else {
        nv->is_smart_coach_on = 0;
        lv_label_set_text(label, (char *)lv_lang_get_text(WATCH_TEXT_ID_OFF));
    }
    exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_smart_coach_t), nv,
                      EXERCISE_WRITE_NV_MODE_REPLACE);
}

static void exercise_goals_distance_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_goals_create(EXERCISE_VALUE_TYPE_DISTANCE);
}

static void exercise_goals_time_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_goals_create(EXERCISE_VALUE_TYPE_TIME);
}

static void exercise_goals_calorie_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_goals_create(EXERCISE_VALUE_TYPE_CALORIE);
}

static void exercise_goals_goallessness_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_GOALS);
    exercise_nv_goals_t * nv = (exercise_nv_goals_t *)(&ext->item->data[offset]);
    if(0 < nv->value) {
        nv->value = 0;
        exercise_write_nv(ext->item->type, offset + sizeof(EXERCISE_VALUE_TYPE_T),
                          sizeof(uint16_t), &nv->value, EXERCISE_WRITE_NV_MODE_REPLACE);
        char txt[EXERCISE_MAX_TXT_LEN] = {};
        exercise_get_goals_txt(nv, txt, EXERCISE_MAX_TXT_LEN);
        lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);

        exercise_setting_ext_t * main_ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING);
        label = lv_obj_get_child(main_ext->par_btn, lv_obj_get_child(main_ext->par_btn, NULL));
        lv_label_set_text(label, txt);
    }

    exercise_del_ui(ACT_ID_EXERCISE_SETTING_1);
}

static void exercise_interval_reminder_get_goals_value(EXERCISE_VALUE_TYPE_T type, uint8_t * value,
                                                       uint8_t value_len)
{
    if(EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM > value_len) {
        printf("%s: warning, value_len %d < %d\n",
               __FUNCTION__, value_len, EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM);
        return;
    }

    if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
        // real value = saved_value / 2;
        uint8_t saved_value[EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM] = {1, 2, 4, 6};
        Hal_Mem_Copy(value, saved_value, EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM);
    } else if(EXERCISE_VALUE_TYPE_TIME == type) {
        uint8_t saved_value[EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM] = {5, 10, 15, 20};
        Hal_Mem_Copy(value, saved_value, EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM);
    }
}

static void exercise_interval_reminder_get_goals_txt(EXERCISE_VALUE_TYPE_T type, char * str,
                                                     uint8_t str_len)
{
    uint8_t value[EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM];
    exercise_interval_reminder_get_goals_value(type, value,
                                               EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM);
    Hal_Mem_Set(str, 0, str_len);
    char temp_str[4];
    if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
        for(uint8_t i = 0;i < EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM;i++) {
            if(0 < i) {
                strcat(str, "\n");
            }
            exercise_int_to_str(value[i] / 2, temp_str, 4);
            strcat(str, temp_str);
            if(0 != (value[i] % 2)) {
                strcat(str, ".5");
            }
        }
    } else if(EXERCISE_VALUE_TYPE_TIME == type) {
        for(uint8_t i = 0;i < EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM;i++) {
            if(0 < i) {
                strcat(str, "\n");
            }
            exercise_int_to_str(value[i], temp_str, 4);
            strcat(str, temp_str);
        }
    }
}

static void exercise_interval_reminder_roller_cb(lv_obj_t * roller, lv_event_t event)
{
    if((LV_EVENT_DRAG_BEGIN == event) || (LV_EVENT_VALUE_CHANGED == event)) {
        EXERCISE_VALUE_TYPE_T type = lv_watch_get_free_num(lv_obj_get_parent(roller));
        char opt[20] = {};
        exercise_interval_reminder_get_goals_txt(type, opt, 20);        
        if(LV_EVENT_VALUE_CHANGED == event) {
            if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
                exercise_roller_opt_ins_txt(lv_roller_get_selected(roller),
                                            (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE),
                                            opt, 20);
            } else if(EXERCISE_VALUE_TYPE_TIME == type) {
                exercise_roller_opt_ins_txt(lv_roller_get_selected(roller),
                                            (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MINS),
                                            opt, 20);
            }
        }
        exercise_set_roller_opt(roller, opt);
    }
}

static void exercise_interval_reminder_ok_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * par = lv_obj_get_parent(btn);
    EXERCISE_VALUE_TYPE_T type = lv_watch_get_free_num(par);
    lv_obj_t * roller = lv_obj_get_child(par, NULL);
    uint16_t index = lv_roller_get_selected(roller);
    if(EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM > index) {
        exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
        exercise_sport_info_t sport_info;
        exercise_get_sport_info(ext->item->type, &sport_info);
        uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                                 EXERCISE_SETTING_ITEM_TYPE_REMINDER);
        exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
        uint8_t value[EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM];
        exercise_interval_reminder_get_goals_value(type, value,
                                                   EXERCISE_MAX_INTERVAL_REMINDER_GOALS_VALUE_NUM);
        if((type != nv->interval.type) || (value[index] != nv->interval.value)) {
            nv->interval.type = type;
            nv->interval.value = value[index];

            exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_interval_reminder_t),
                              &nv->interval, EXERCISE_WRITE_NV_MODE_REPLACE);

            char txt[EXERCISE_MAX_TXT_LEN] = {};
            exercise_get_interval_reminder_txt(nv, txt, EXERCISE_MAX_TXT_LEN);
            lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
            lv_label_set_text(label, txt);
        }
    } else {
        printf("%s: warning, invalid index %d\n", __FUNCTION__, index);
    }
    exercise_del_ui(ACT_ID_EXERCISE_SETTING_3);
    exercise_del_ui(ACT_ID_EXERCISE_SETTING_2);
}

static lv_obj_t * exercise_interval_reminder_goals_create(EXERCISE_VALUE_TYPE_T type)
{
    printf("%s\n", __FUNCTION__);

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_3;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    uint8_t sel_id = 1;
    char opt[20] = {};
    uint16_t txt_id = WATCH_TEXT_ID_DISTANCE;
    exercise_interval_reminder_get_goals_txt(type, opt, 20);
    if(EXERCISE_VALUE_TYPE_DISTANCE == type) {
        exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE),
                                    opt, 20);
    } else if(EXERCISE_VALUE_TYPE_TIME == type) {
        exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_ACT_MINS),
                                    opt, 20);
        txt_id = WATCH_TEXT_ID_TIME;
    }
    exercise_roller_ui_desc_t desc = {obj, opt, EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_id,
                                      txt_id, WATCH_TEXT_ID_OK, LV_COLOR_MAKE(0, 255, 64),
                                      exercise_interval_reminder_roller_cb,
                                      exercise_interval_reminder_ok_cb};
    lv_obj_t * roller = exercise_add_roller_ui(&desc);
    lv_watch_set_free_num(lv_obj_get_parent(roller), type);

    return(obj);
}

static void exercise_interval_reminder_distance_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_interval_reminder_goals_create(EXERCISE_VALUE_TYPE_DISTANCE);
}

static void exercise_interval_reminder_time_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_interval_reminder_goals_create(EXERCISE_VALUE_TYPE_TIME);
}

static void exercise_get_hr_reminder_txt(exercise_nv_reminder_t * nv, char * txt, uint32_t txt_len)
{
    if((NULL == nv) || (NULL == txt) || (0 == txt_len)) return;

    if(0 == nv->hr.is_on) {
        snprintf(txt, txt_len, "%s", (char *)lv_lang_get_text(WATCH_TEXT_ID_OFF));
    } else {
        snprintf(txt, txt_len, "%d-%d%s", nv->hr.min_value, nv->hr.max_value,
                 (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
    }
}

static void exercise_interval_reminder_goallessness_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }
    printf("%s\n", __FUNCTION__);
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
    if(0 < nv->interval.value) {
        nv->interval.value = 0;
        exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_interval_reminder_t),
                          &nv->interval, EXERCISE_WRITE_NV_MODE_REPLACE);
        char txt[EXERCISE_MAX_TXT_LEN] = {};
        exercise_get_interval_reminder_txt(nv, txt, EXERCISE_MAX_TXT_LEN);
        lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);
    }
    exercise_del_ui(ACT_ID_EXERCISE_SETTING_2);
}

static lv_obj_t * exercise_interval_reminder_create(lv_obj_t * par_btn)
{
    printf("%s\n", __FUNCTION__);

    lv_obj_t * prim_label = lv_obj_get_child_back(par_btn, NULL);
    exercise_item_t * item = lv_watch_get_free_ptr(prim_label);
    if(NULL == item) {
        printf("%s: warning, item is NULL\n", __FUNCTION__);
        return(NULL);
    }

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_2;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    exercise_setting_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(exercise_setting_ext_t));
    LV_ASSERT_MEM(ext);

    ext->par_btn = par_btn;
    ext->item = item;

    exercise_sport_info_t sport_info;
    exercise_get_sport_info(item->type, &sport_info);

    lv_obj_t * list = exercise_add_list_ui(obj, 4);

    exercise_item_info_t item_info = {};

    item_info.prim_txt_id = WATCH_TEXT_ID_INTERVAL_REMINDER;
    exercise_add_list_btn(NULL, list, &item_info);

    if(0 != (sport_info.interval_reminder_item_bitmap & (1 << EXERCISE_VALUE_TYPE_DISTANCE))) {
        item_info.img_src = ICON_EXERCISE_DISTANCE;
        item_info.img_bg_color = LV_COLOR_GREEN;
        item_info.prim_txt_id = WATCH_TEXT_ID_DISTANCE;
        item_info.cb = exercise_interval_reminder_distance_cb;
        item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
        exercise_add_list_btn(NULL, list, &item_info);
    }

    if(0 != (sport_info.interval_reminder_item_bitmap & (1 << EXERCISE_VALUE_TYPE_TIME))) {
        item_info.img_src = ICON_EXERCISE_TIME;
        item_info.img_bg_color = LV_COLOR_MAKE(0x00, 0x80, 0x80);
        item_info.prim_txt_id = WATCH_TEXT_ID_TIME;
        item_info.cb = exercise_interval_reminder_time_cb;
        exercise_add_list_btn(NULL, list, &item_info);
    }

    item_info.img_src = ICON_EXERCISE_NO_GOALS;
    item_info.img_bg_color = LV_COLOR_MAKE(0x27, 0xAD, 0xFE);
    item_info.prim_txt_id = WATCH_TEXT_ID_GOALLESSNESS;
    item_info.cb = exercise_interval_reminder_goallessness_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_NORMAL;
    exercise_add_list_btn(NULL, list, &item_info);

    exercise_add_empty_list_btn(list);

    return(obj);
}

static void exercise_hr_range_custom_roller_cb(lv_obj_t * roller, lv_event_t event)
{
    if((LV_EVENT_DRAG_BEGIN == event) || (LV_EVENT_VALUE_CHANGED == event)) {
        uint8_t sel_value = lv_watch_get_free_num(roller);
        if(LV_EVENT_VALUE_CHANGED == event) {
            char sel_str[10] = {};
            lv_roller_get_selected_str(roller, sel_str, 10);
            if(0 < strlen(sel_str)) {
                sel_value = exercise_str_to_int(sel_str);
                lv_watch_set_free_num(roller, sel_value);
            }
        }
        lv_obj_t * btn = lv_obj_get_child(lv_obj_get_parent(roller), roller);
        lv_obj_t * label = lv_obj_get_child(btn, NULL);
        char opt[100];
        uint8_t sel_id;
        if(WATCH_TEXT_ID_NEXT_STEP == label->user_data.user_num) {
            sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_value,
                                                    EXERCISE_CUSTOM_HR_STEP, opt, 100,
                                                    EXERCISE_MIN_HR_HIGH_VALUE,
                                                    EXERCISE_MAX_HR_HIGH_VALUE, false,
                                                    LV_BIDI_DIR_AUTO);
        } else {
            uint8_t high_value = lv_watch_get_free_num(btn);
            uint8_t max_low_value = high_value - EXERCISE_MAX_HR_LOW_VALUE_BELOW_HIGH_VALUE;
            sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_value,
                                                    EXERCISE_CUSTOM_HR_STEP, opt, 100,
                                                    EXERCISE_MIN_HR_LOW_VALUE, max_low_value,
                                                    false, LV_BIDI_DIR_AUTO);
        }

        if(LV_EVENT_VALUE_CHANGED == event) {
            exercise_roller_opt_ins_txt(sel_id,
                                        (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN),
                                        opt, 100);
        }
        exercise_set_roller_opt(roller, opt);
        lv_roller_set_selected(roller, sel_id, false);
    }
}

static void exercise_hr_range_custom_ok_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * par = lv_obj_get_parent(btn);
    lv_obj_t * roller = lv_obj_get_child(par, NULL);
    uint8_t value = lv_watch_get_free_num(roller);
    lv_obj_t * label = lv_obj_get_child(btn, NULL);
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_3);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
    if(WATCH_TEXT_ID_NEXT_STEP == label->user_data.user_num) {
        lv_obj_t * label_title = lv_obj_get_child_back(par, NULL);
        lv_label_set_text_id(label_title, WATCH_TEXT_ID_LOW_HEART_RATE_VALUE);

        uint8_t init_low_value = EXERCISE_DEF_HR_LOW_VALUE;
        uint8_t max_low_value = value - EXERCISE_MAX_HR_LOW_VALUE_BELOW_HIGH_VALUE;
        if((EXERCISE_MIN_HR_LOW_VALUE <= nv->hr.min_value)
           && (max_low_value >= nv->hr.min_value)) {
            init_low_value = nv->hr.min_value;
        }
        char opt[100];
        uint8_t sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT,
                                                        init_low_value,
                                                        EXERCISE_CUSTOM_HR_STEP, opt, 100,
                                                        EXERCISE_MIN_HR_LOW_VALUE, max_low_value,
                                                        false, LV_BIDI_DIR_AUTO);
        exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN),
                                    opt, 100);
        exercise_set_roller_opt(roller, opt);
        lv_roller_set_selected(roller, sel_id, false);
        lv_watch_set_free_num(roller, init_low_value);
        lv_watch_set_free_num(btn, value);
        lv_coord_t label_y = lv_obj_get_y(label);
        lv_label_set_text_id(label, WATCH_TEXT_ID_OK);
        lv_obj_align(label, btn, LV_ALIGN_IN_TOP_MID, 0, label_y);
    } else {
        nv->hr.range_type = EXERCISE_HR_RANE_TYPE_CUSTOM;
        nv->hr.min_value = value;
        nv->hr.max_value = lv_watch_get_free_num(btn);

        exercise_write_nv(ext->item->type, offset + sizeof(exercise_nv_interval_reminder_t),
                          sizeof(exercise_nv_hr_reminder_t), &nv->hr,
                          EXERCISE_WRITE_NV_MODE_REPLACE);

        char txt[50] = {};
        snprintf(txt, 50, "%d-%d%s", nv->hr.min_value, nv->hr.max_value,
                 (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
        label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);

        if(0 != nv->hr.is_on) {
            ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
            label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
            lv_label_set_text(label, txt);
        }

        exercise_del_ui(ACT_ID_EXERCISE_SETTING_4);
        exercise_del_ui(ACT_ID_EXERCISE_SETTING_3);
    }
}

static lv_obj_t * exercise_hr_reminder_range_custom_create(void)
{
    printf("%s\n", __FUNCTION__);

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_4;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    uint16_t init_value = EXERCISE_DEF_HR_HIGH_VALUE;
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_3);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    exercise_nv_reminder_t * nv
        = (exercise_nv_reminder_t *)exercise_get_nv_pointer(ext->item->data,
                                                            sport_info.setting_item_bitmap,
                                                            EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    if((EXERCISE_MIN_HR_HIGH_VALUE <= nv->hr.max_value)
       && (EXERCISE_MAX_HR_HIGH_VALUE >= nv->hr.max_value)) {
        init_value = nv->hr.max_value;
    }
    char opt[100];
    uint8_t sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT, init_value,
                                                    EXERCISE_CUSTOM_HR_STEP, opt, 100,
                                                    EXERCISE_MIN_HR_HIGH_VALUE,
                                                    EXERCISE_MAX_HR_HIGH_VALUE, false,
                                                    LV_BIDI_DIR_AUTO);
    exercise_roller_opt_ins_txt(sel_id, (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN),
                                opt, 100);
    exercise_roller_ui_desc_t desc = {obj, opt, EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_id,
                                      WATCH_TEXT_ID_HIGH_HEART_RATE_VALUE, WATCH_TEXT_ID_NEXT_STEP,
                                      LV_COLOR_ORANGE, exercise_hr_range_custom_roller_cb,
                                      exercise_hr_range_custom_ok_cb};
    lv_obj_t * roller = exercise_add_roller_ui(&desc);
    lv_watch_set_free_num(roller, init_value);

    return(obj);
}

static void exercise_hr_reminder_range_custom_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    exercise_hr_reminder_range_custom_create();
}

static void exercise_hr_reminder_range_cb_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * cb = lv_obj_get_child(btn, NULL);

    if(LV_BTN_STATE_RELEASED != lv_checkbox_get_state(cb)) return;

    lv_checkbox_set_checked(cb, true);

    EXERCISE_HR_RANE_TYPE_T range_type = lv_watch_get_free_num(btn);
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_3);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
    printf("%s: range type new %d, old %d\n", __FUNCTION__, range_type, nv->hr.range_type);

    if(EXERCISE_HR_RANE_TYPE_CUSTOM != nv->hr.range_type) {
        lv_obj_t * par = lv_obj_get_parent(btn);
        if(nv->hr.range_type > range_type) {
            lv_obj_t * child_btn = lv_obj_get_child_back(par, btn);
            while(child_btn) {
                if(nv->hr.range_type == lv_watch_get_free_num(child_btn)) {
                    cb = lv_obj_get_child(child_btn, NULL);
                    lv_checkbox_set_checked(cb, false);
                    break;
                }
                child_btn = lv_obj_get_child_back(par, child_btn);
            }
        } else {
            lv_obj_t * child_btn = lv_obj_get_child(par, btn);
            while(child_btn) {
                if(nv->hr.range_type == lv_watch_get_free_num(child_btn)) {
                    cb = lv_obj_get_child(child_btn, NULL);
                    lv_checkbox_set_checked(cb, false);
                    break;
                }
                child_btn = lv_obj_get_child(par, child_btn);
            }
        }
    }

    nv->hr.range_type = range_type;
    switch(range_type) {
    case EXERCISE_HR_RANE_TYPE_EFFICIENT_FAT_BURNING:
        nv->hr.min_value = EXERCISE_EFFICIENT_FAT_BURNING_HR_LOW_VALUE;
        nv->hr.max_value = EXERCISE_EFFICIENT_FAT_BURNING_HR_HIGH_VALUE;
        break;

    case EXERCISE_HR_RANE_TYPE_HEART_LUNG_IMPROVE:
        nv->hr.min_value = EXERCISE_HEART_LUNG_IMPROVE_HR_LOW_VALUE;
        nv->hr.max_value = EXERCISE_HEART_LUNG_IMPROVE_HR_HIGH_VALUE;
        break;

    case EXERCISE_HR_RANE_TYPE_RECOVERY_TRAINING:
        nv->hr.min_value = EXERCISE_RECOVERY_TRAINING_HR_LOW_VALUE;
        nv->hr.max_value = EXERCISE_RECOVERY_TRAINING_HR_HIGH_VALUE;
        break;
    }
    exercise_write_nv(ext->item->type,
                      offset + sizeof(exercise_nv_interval_reminder_t),
                      sizeof(exercise_nv_hr_reminder_t), &nv->hr, EXERCISE_WRITE_NV_MODE_REPLACE);

    char txt[50] = {};
    snprintf(txt, 50, "%d-%d%s", nv->hr.min_value, nv->hr.max_value,
             (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
    lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
    lv_label_set_text(label, txt);

    if(0 != nv->hr.is_on) {
        ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
        label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);
    }
}

static lv_obj_t * exercise_hr_reminder_range_create(lv_obj_t * par_btn)
{
    printf("%s\n", __FUNCTION__);

    lv_obj_t * prim_label = lv_obj_get_child_back(par_btn, NULL);
    exercise_item_t * item = lv_watch_get_free_ptr(prim_label);
    if(NULL == item) {
        printf("%s: warning, item is NULL\n", __FUNCTION__);
        return(NULL);
    }

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_3;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    exercise_setting_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(exercise_setting_ext_t));
    LV_ASSERT_MEM(ext);

    ext->par_btn = par_btn;
    ext->item = item;

    lv_obj_t * list = exercise_add_list_ui(obj, 4);

    exercise_item_info_t item_info = {};
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(item->type, &sport_info);
    exercise_nv_reminder_t * nv
        = (exercise_nv_reminder_t *)exercise_get_nv_pointer(item->data,
                                                            sport_info.setting_item_bitmap,
                                                            EXERCISE_SETTING_ITEM_TYPE_REMINDER);

    item_info.prim_txt_id = WATCH_TEXT_ID_HEART_RATE_RANGE;
    exercise_add_list_btn(NULL, list, &item_info);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_EFFICIENT_FAT_BURNING;
    snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d-%d%s",
             EXERCISE_EFFICIENT_FAT_BURNING_HR_LOW_VALUE,
             EXERCISE_EFFICIENT_FAT_BURNING_HR_HIGH_VALUE,
             (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
    item_info.cb = exercise_hr_reminder_range_cb_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_CHECKBOX;
    if(EXERCISE_HR_RANE_TYPE_EFFICIENT_FAT_BURNING == nv->hr.range_type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    lv_obj_t * btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_HR_RANE_TYPE_EFFICIENT_FAT_BURNING);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_HEART_LUNG_IMPROVE;
    snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d-%d%s",
             EXERCISE_HEART_LUNG_IMPROVE_HR_LOW_VALUE,
             EXERCISE_HEART_LUNG_IMPROVE_HR_HIGH_VALUE,
             (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
    if(EXERCISE_HR_RANE_TYPE_HEART_LUNG_IMPROVE == nv->hr.range_type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_HR_RANE_TYPE_HEART_LUNG_IMPROVE);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_RECOVERY_TRAINING;
    snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d-%d%s",
             EXERCISE_RECOVERY_TRAINING_HR_LOW_VALUE,
             EXERCISE_RECOVERY_TRAINING_HR_HIGH_VALUE,
             (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
    if(EXERCISE_HR_RANE_TYPE_RECOVERY_TRAINING == nv->hr.range_type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_HR_RANE_TYPE_RECOVERY_TRAINING);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_CUSTOM;
    item_info.sec_txt[0] = '\0';
    item_info.cb = exercise_hr_reminder_range_custom_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_BTN;
    exercise_add_list_btn(NULL, list, &item_info);

    exercise_add_empty_list_btn(list);

    return(obj);
}

static void exercise_hr_reminder_range_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    exercise_nv_reminder_t * nv
        = (exercise_nv_reminder_t *)exercise_get_nv_pointer(ext->item->data,
                                                            sport_info.setting_item_bitmap,
                                                            EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    if(0 == nv->hr.is_on) return;

    exercise_hr_reminder_range_create(btn);
}

static void exercise_hr_reminder_sw_cb(lv_obj_t * sw, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    bool state = lv_switch_get_state(sw);
    printf("%s: new state %d\n", __FUNCTION__, state);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
    lv_obj_t * btn = lv_obj_get_parent(sw);
    lv_obj_t * hr_range_btn = lv_obj_get_child_back(lv_obj_get_parent(btn), btn);
    lv_obj_t * label = lv_obj_get_child_back(hr_range_btn, NULL);
    if(state) {
        nv->hr.is_on = 1;
        lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                          LV_COLOR_WHITE);
    } else {
        nv->hr.is_on = 0;
        lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                          LV_COLOR_GRAY);
    }
    char txt[50] = {};
    exercise_get_hr_reminder_txt(nv, txt, 50);
    label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
    lv_label_set_text(label, txt);
    exercise_write_nv(ext->item->type, offset + sizeof(exercise_nv_interval_reminder_t),
                      1, &nv->hr.is_on, EXERCISE_WRITE_NV_MODE_REPLACE);
}

static lv_obj_t * exercise_hr_reminder_create(lv_obj_t * par_btn)
{
    printf("%s\n", __FUNCTION__);

    lv_obj_t * prim_label = lv_obj_get_child_back(par_btn, NULL);
    exercise_item_t * item = lv_watch_get_free_ptr(prim_label);
    if(NULL == item) {
        printf("%s: warning, item is NULL\n", __FUNCTION__);
        return(NULL);
    }

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_2;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    exercise_setting_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(exercise_setting_ext_t));
    LV_ASSERT_MEM(ext);

    ext->par_btn = par_btn;
    ext->item = item;

    lv_obj_t * list = exercise_add_list_ui(obj, 4);

    exercise_item_info_t item_info = {};
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(item->type, &sport_info);
    exercise_nv_reminder_t * nv
        = (exercise_nv_reminder_t *)exercise_get_nv_pointer(ext->item->data,
                                                            sport_info.setting_item_bitmap,
                                                            EXERCISE_SETTING_ITEM_TYPE_REMINDER);

    item_info.prim_txt_id = WATCH_TEXT_ID_HEART_RATE_REMINDER;
    exercise_add_list_btn(NULL, list, &item_info);

    item_info.img_src = ICON_EXERCISE_HEART_RATE;
    item_info.img_bg_color = LV_COLOR_MAKE(0xB0, 0x00, 0x00);
    item_info.type = EXERCISE_LIST_BTN_TYPE_SWITCH;
    if(0 == nv->hr.is_on) {
        item_info.is_on = false;
    } else {
        item_info.is_on = true;
    }
    item_info.setting_cb = exercise_hr_reminder_sw_cb;
    exercise_add_list_btn(NULL, list, &item_info);

    item_info.img_src = ICON_EXERCISE_HEART_RATE;
    item_info.img_bg_color = LV_COLOR_MAKE(0xB0, 0x00, 0x00);
    item_info.prim_txt_id = WATCH_TEXT_ID_HEART_RATE_RANGE;
    snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d-%d%s", nv->hr.min_value,
             nv->hr.max_value, (char *)lv_lang_get_text(WATCH_TEXT_ID_BEATS_PER_MIN));
    item_info.cb = exercise_hr_reminder_range_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
    lv_obj_t * btn = exercise_add_list_btn(item, list, &item_info);
    if(0 == nv->hr.is_on) {
        lv_obj_t * label = lv_obj_get_child_back(btn, NULL);
        lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                          LV_COLOR_GRAY);
    }
    exercise_add_empty_list_btn(list);

    return(obj);
}

static void exercise_interval_reminder_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    exercise_interval_reminder_create(btn);
}

static void exercise_hr_reminder_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    exercise_hr_reminder_create(btn);
}

static void exercise_training_effect_cb(lv_obj_t * sw, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    bool state = lv_switch_get_state(sw);
    printf("%s: new state %d\n", __FUNCTION__, state);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
    if(state) {
        nv->is_training_effect_on = 1;
    } else {
        nv->is_training_effect_on = 0;
    }
    exercise_write_nv(ext->item->type,
                      offset + sizeof(exercise_nv_interval_reminder_t) +
                      sizeof(exercise_nv_hr_reminder_t),
                      1, &nv->is_training_effect_on, EXERCISE_WRITE_NV_MODE_REPLACE);
}

static void exercise_physical_state_cb(lv_obj_t * sw, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    bool state = lv_switch_get_state(sw);
    printf("%s: new state %d\n", __FUNCTION__, state);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_REMINDER);
    exercise_nv_reminder_t * nv = (exercise_nv_reminder_t *)(&ext->item->data[offset]);
    if(state) {
        nv->is_physical_state_on = 1;
    } else {
        nv->is_physical_state_on = 0;
    }
    exercise_write_nv(ext->item->type,
                      offset + sizeof(exercise_nv_interval_reminder_t) +
                      sizeof(exercise_nv_hr_reminder_t) + 1,
                      1, &nv->is_physical_state_on, EXERCISE_WRITE_NV_MODE_REPLACE);
}

static void exercise_get_intel_run_comp_txt(exercise_nv_intel_run_comp_t * nv,
                                            lv_bidi_dir_t dir, char * txt, uint32_t txt_len)
{
    if((NULL == txt) || (0 == txt_len)) return;

    if(0 == nv->is_on) {
        snprintf(txt, txt_len, "%s", (char *)lv_lang_get_text(WATCH_TEXT_ID_OFF));
    } else {
        if(LV_BIDI_DIR_RTL == dir) {
            snprintf(txt, txt_len, "\"%02d'%d/%s", nv->value % 60, nv->value / 60,
                     (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
        } else {
            snprintf(txt, txt_len, "%d'%02d\"/%s", nv->value / 60, nv->value % 60,
                     (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
        }
    }
}

static void exercise_goal_pace_custom_roller_cb(lv_obj_t * roller, lv_event_t event)
{
    if((LV_EVENT_DRAG_BEGIN == event) || (LV_EVENT_VALUE_CHANGED == event)) {
        uint16_t sel_value = 0;
        if(LV_EVENT_DRAG_BEGIN == event) {
            sel_value = lv_watch_get_free_num(roller);
        } else {
            char sel_str[10] = {};
            lv_roller_get_selected_str(roller, sel_str, 10);
            int8_t i = strlen(sel_str) - 1;
            for(;i >= 0;i--) {
                if(('0' <= sel_str[i]) && ('9' >= sel_str[i])) {
                    break;
                }
            }
            uint32_t multiple = 0;
            uint8_t coef;
            if(LV_BIDI_DIR_RTL == lv_obj_get_base_dir(roller)) {
                // for example: "\"35'12" or "\"35'12/km" == 12'35"
                coef = 60; // for minute
            } else {
                // for example: "12'35\"" or "12'35\"/km" == 12'35"
                coef = 1;  // for second
            }
            for(;(i >= 0) && ('"' != sel_str[i]);i--) {
                if('\'' == sel_str[i]) {
                    multiple = 0;
                    coef = (1 == coef) ? 60 : 1;
                    continue;
                }
                if(0 == multiple) {
                    multiple = 1;
                } else {
                    multiple *= 10;
                }
                sel_value += (sel_str[i] - '0') * multiple * coef;
            }
            lv_watch_set_free_num(roller, sel_value);
        }
        char opt[100];
        uint8_t sel_id = exercise_roller_get_custom_opt
            (EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_value,
             EXERCISE_GOAL_PACE_CUSTOM_TIME_STEP, opt, 100,
             EXERCISE_GOAL_PACE_CUSTOM_MIN_TIME_VALUE,
             EXERCISE_GOAL_PACE_CUSTOM_MAX_TIME_VALUE, true, lv_obj_get_base_dir(roller));

        if(LV_EVENT_VALUE_CHANGED == event) {
            char txt[10] = {"/"};
            strcat(txt, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
            exercise_roller_opt_ins_txt(sel_id, txt, opt, 100);
        }
        exercise_set_roller_opt(roller, opt);
        lv_roller_set_selected(roller, sel_id, false);
    }
}

static void exercise_goal_pace_custom_ok_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * par = lv_obj_get_parent(btn);
    lv_obj_t * roller = lv_obj_get_child(par, NULL);
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP);
    exercise_nv_intel_run_comp_t * nv = (exercise_nv_intel_run_comp_t *)(&ext->item->data[offset]);
    nv->type = EXERCISE_GOAL_PACE_TYPE_CUSTOM;
    nv->value = lv_watch_get_free_num(roller);

    exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_intel_run_comp_t), &nv,
                      EXERCISE_WRITE_NV_MODE_REPLACE);

    char txt[50] = {};
    snprintf(txt, 50, "%d'%02d\"/%s", nv->value / 60, nv->value % 60,
             (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
    lv_label_set_text(label, txt);

    if(0 != nv->is_on) {
        ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
        label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);
    }

    exercise_del_ui(ACT_ID_EXERCISE_SETTING_3);
    exercise_del_ui(ACT_ID_EXERCISE_SETTING_2);
}

static lv_obj_t * exercise_goal_pace_custom_create(void)
{
    printf("%s\n", __FUNCTION__);

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_3;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    uint16_t init_value = EXERCISE_GOAL_PACE_CUSTOM_DEF_TIME_VALUE;
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    exercise_nv_intel_run_comp_t * nv
        = (exercise_nv_intel_run_comp_t *)exercise_get_nv_pointer(ext->item->data,
                                                                  sport_info.setting_item_bitmap,
                                                                  EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP);
    if((EXERCISE_GOAL_PACE_CUSTOM_MIN_TIME_VALUE <= nv->value)
       && (EXERCISE_GOAL_PACE_CUSTOM_MAX_TIME_VALUE >= nv->value)) {
        init_value = nv->value;
    }
    char opt[100];
    uint8_t sel_id = exercise_roller_get_custom_opt(EXERCISE_ROLLER_VISIBLE_ROW_COUNT, init_value,
                                                    EXERCISE_GOAL_PACE_CUSTOM_TIME_STEP, opt, 100,
                                                    EXERCISE_GOAL_PACE_CUSTOM_MIN_TIME_VALUE,
                                                    EXERCISE_GOAL_PACE_CUSTOM_MAX_TIME_VALUE, true,
                                                    lv_obj_get_base_dir(obj));
    char txt[10] = {"/"};
    strcat(txt, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    exercise_roller_opt_ins_txt(sel_id, txt, opt, 100);
    exercise_roller_ui_desc_t desc = {obj, opt, EXERCISE_ROLLER_VISIBLE_ROW_COUNT, sel_id,
                                      WATCH_TEXT_ID_GOAL_PACE, WATCH_TEXT_ID_OK,
                                      LV_COLOR_ORANGE, exercise_goal_pace_custom_roller_cb,
                                      exercise_goal_pace_custom_ok_cb};
    lv_obj_t * roller = exercise_add_roller_ui(&desc);
    lv_watch_set_free_num(roller, init_value);

    return(obj);
}

static void exercise_goal_pace_cb_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    lv_obj_t * cb = lv_obj_get_child(btn, NULL);

    if(LV_BTN_STATE_RELEASED != lv_checkbox_get_state(cb)) return;

    lv_checkbox_set_checked(cb, true);

    EXERCISE_GOAL_PACE_TYPE_T type = lv_watch_get_free_num(btn);
    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_2);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP);
    exercise_nv_intel_run_comp_t * nv = (exercise_nv_intel_run_comp_t *)(&ext->item->data[offset]);
    printf("%s: type new %d, old %d\n", __FUNCTION__, type, nv->type);

    if(EXERCISE_GOAL_PACE_TYPE_CUSTOM != nv->type) {
        lv_obj_t * par = lv_obj_get_parent(btn);
        if(nv->type > type) {
            lv_obj_t * child_btn = lv_obj_get_child_back(par, btn);
            while(child_btn) {
                if(nv->type == lv_watch_get_free_num(child_btn)) {
                    cb = lv_obj_get_child(child_btn, NULL);
                    lv_checkbox_set_checked(cb, false);
                    break;
                }
                child_btn = lv_obj_get_child_back(par, child_btn);
            }
        } else {
            lv_obj_t * child_btn = lv_obj_get_child(par, btn);
            while(child_btn) {
                if(nv->type == lv_watch_get_free_num(child_btn)) {
                    cb = lv_obj_get_child(child_btn, NULL);
                    lv_checkbox_set_checked(cb, false);
                    break;
                }
                child_btn = lv_obj_get_child(par, child_btn);
            }
        }
    }

    nv->type = type;
    switch(type) {
    case EXERCISE_GOAL_PACE_TYPE_LAST_TIME:
        nv->value = nv->last_value;
        break;

    case EXERCISE_GOAL_PACE_TYPE_SINGLE_BEST:
        nv->value = nv->best_value;
        break;

    case EXERCISE_GOAL_PACE_TYPE_GENERAL_LEVEL:
        nv->value = EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE;
        break;

    case EXERCISE_GOAL_PACE_TYPE_EXPERT_LEVEL:
        nv->value = EXERCISE_GOAL_PACE_EXPERT_TIME_VALUE;
        break;
    }
    exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_intel_run_comp_t), &nv,
                      EXERCISE_WRITE_NV_MODE_REPLACE);

    char txt[50] = {};
    snprintf(txt, 50, "%d'%02d\"/%s", nv->value / 60, nv->value % 60,
             (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    lv_obj_t * label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
    lv_label_set_text(label, txt);

    if(0 != nv->is_on) {
        ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
        label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
        lv_label_set_text(label, txt);
    }
}

static void exercise_goal_pace_custom_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    exercise_goal_pace_custom_create();
}

static lv_obj_t * exercise_goal_pace_create(lv_obj_t * par_btn)
{
    printf("%s\n", __FUNCTION__);

    lv_obj_t * prim_label = lv_obj_get_child_back(par_btn, NULL);
    exercise_item_t * item = lv_watch_get_free_ptr(prim_label);
    if(NULL == item) {
        printf("%s: warning, item is NULL\n", __FUNCTION__);
        return(NULL);
    }

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_2;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    exercise_setting_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(exercise_setting_ext_t));
    LV_ASSERT_MEM(ext);

    ext->par_btn = par_btn;
    ext->item = item;

    lv_bidi_dir_t dir = lv_obj_get_base_dir(obj);

    lv_obj_t * list = exercise_add_list_ui(obj, 4);

    exercise_item_info_t item_info = {};
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(item->type, &sport_info);
    exercise_nv_intel_run_comp_t * nv
        = (exercise_nv_intel_run_comp_t *)exercise_get_nv_pointer(item->data,
                                                                  sport_info.setting_item_bitmap,
                                                                  EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP);

    item_info.prim_txt_id = WATCH_TEXT_ID_GOAL_PACE;
    exercise_add_list_btn(NULL, list, &item_info);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_LAST_TIME;
    if(LV_BIDI_DIR_RTL == dir) {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "\"%02d'%d/%s", nv->last_value % 60,
                 nv->last_value / 60, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    } else {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d'%02d\"/%s", nv->last_value / 60,
                 nv->last_value % 60, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    }
    item_info.cb = exercise_goal_pace_cb_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_CHECKBOX;
    if(EXERCISE_GOAL_PACE_TYPE_LAST_TIME == nv->type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    lv_obj_t * btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_GOAL_PACE_TYPE_LAST_TIME);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_SINGLE_BEST;
    if(LV_BIDI_DIR_RTL == dir) {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "\"%02d'%d/%s", nv->best_value % 60,
                 nv->best_value / 60, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    } else {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d'%02d\"/%s", nv->best_value / 60,
                 nv->best_value % 60, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    }
    if(EXERCISE_GOAL_PACE_TYPE_SINGLE_BEST == nv->type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_GOAL_PACE_TYPE_SINGLE_BEST);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_MASS_LEVEL;
    if(LV_BIDI_DIR_RTL == dir) {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "\"%02d'%d/%s",
                 EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE % 60,
                 EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE / 60,
                 (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    } else {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d'%02d\"/%s",
                 EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE / 60,
                 EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE % 60,
                 (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    }
    if(EXERCISE_GOAL_PACE_TYPE_GENERAL_LEVEL == nv->type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_GOAL_PACE_TYPE_GENERAL_LEVEL);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_EXPERT_LEVEL;
    if(LV_BIDI_DIR_RTL == dir) {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "\"%02d'%d/%s",
                 EXERCISE_GOAL_PACE_EXPERT_TIME_VALUE % 60,
                 EXERCISE_GOAL_PACE_EXPERT_TIME_VALUE / 60,
                 (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    } else {
        snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d'%02d\"/%s",
                 EXERCISE_GOAL_PACE_EXPERT_TIME_VALUE / 60,
                 EXERCISE_GOAL_PACE_EXPERT_TIME_VALUE % 60,
                 (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
    }
    if(EXERCISE_GOAL_PACE_TYPE_EXPERT_LEVEL == nv->type) {
        item_info.is_on = true;
    } else {
        item_info.is_on = false;
    }
    btn = exercise_add_list_btn(NULL, list, &item_info);
    lv_watch_set_free_num(btn, EXERCISE_GOAL_PACE_TYPE_EXPERT_LEVEL);

    item_info.img_src = NULL;
    item_info.prim_txt_id = WATCH_TEXT_ID_CUSTOM;
    item_info.sec_txt[0] = '\0';
    item_info.cb = exercise_goal_pace_custom_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_BTN;
    exercise_add_list_btn(NULL, list, &item_info);

    exercise_add_empty_list_btn(list);

    return(obj);
}

static void exercise_intel_run_comp_sw_cb(lv_obj_t * sw, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    bool state = lv_switch_get_state(sw);
    printf("%s: new state %d\n", __FUNCTION__, state);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP);
    exercise_nv_intel_run_comp_t * nv = (exercise_nv_intel_run_comp_t *)(&ext->item->data[offset]);
    lv_obj_t * btn = lv_obj_get_parent(sw);
    lv_obj_t * goal_pace_btn = lv_obj_get_child_back(lv_obj_get_parent(btn), btn);
    lv_obj_t * label = lv_obj_get_child_back(goal_pace_btn, NULL);
    if(state) {
        nv->is_on = 1;
        lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                          LV_COLOR_WHITE);
    } else {
        nv->is_on = 0;
        lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                          LV_COLOR_GRAY);
    }
    char txt[50] = {};
    exercise_get_intel_run_comp_txt(nv, lv_obj_get_base_dir(sw), txt, 50);
    label = lv_obj_get_child(ext->par_btn, lv_obj_get_child(ext->par_btn, NULL));
    lv_label_set_text(label, txt);
    exercise_write_nv(ext->item->type, offset, 1, &nv->is_on, EXERCISE_WRITE_NV_MODE_REPLACE);
}

static void exercise_goal_pace_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING_1);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    exercise_nv_intel_run_comp_t * nv
        = (exercise_nv_intel_run_comp_t *)exercise_get_nv_pointer(ext->item->data,
                                                                  sport_info.setting_item_bitmap,
                                                                  EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP);
    if(0 == nv->is_on) return;

    exercise_goal_pace_create(btn);
}

static lv_obj_t * exercise_setting_1_create(lv_obj_t * par_btn)
{
    printf("%s\n", __FUNCTION__);

    lv_obj_t * prim_label = lv_obj_get_child_back(par_btn, NULL);
    exercise_item_t * item = lv_watch_get_free_ptr(prim_label);
    if(NULL == item) {
        printf("%s: warning, item is NULL\n", __FUNCTION__);
        return(NULL);
    }

    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING_1;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    exercise_setting_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(exercise_setting_ext_t));
    LV_ASSERT_MEM(ext);

    ext->par_btn = par_btn;
    ext->item = item;

    exercise_sport_info_t sport_info;
    exercise_get_sport_info(item->type, &sport_info);

    lv_obj_t * list = exercise_add_list_ui(obj, 4);

    lv_obj_t * btn;
    exercise_item_info_t item_info = {};
    EXERCISE_SETTING_ITEM_TYPE_T item_type = lv_watch_get_free_num(par_btn);
    if(EXERCISE_SETTING_ITEM_TYPE_SMART_COACH == item_type) {
        printf("%s: smart coach\n", __FUNCTION__);

        item_info.prim_txt_id = WATCH_TEXT_ID_SMART_COACH;
        exercise_add_list_btn(NULL, list, &item_info);

        item_info.img_src = ICON_EXERCISE_SMART_COACH;
        item_info.img_bg_color = LV_COLOR_MAKE(0x00, 0xA0, 0xA0);
        item_info.type = EXERCISE_LIST_BTN_TYPE_SWITCH;
        exercise_nv_smart_coach_t * nv
            = (exercise_nv_smart_coach_t *)exercise_get_nv_pointer(item->data,
                                                                   sport_info.setting_item_bitmap,
                                                                   item_type);
        if(1 == nv->is_smart_coach_on) {
            item_info.is_on = true;
        } else {
            item_info.is_on = false;
        }
        item_info.setting_cb = exercise_smart_coach_sw_cb;
        exercise_add_list_btn(NULL, list, &item_info);

        exercise_add_list_btn_with_long_txt
            (list, (char *)lv_lang_get_text(WATCH_TEXT_ID_SMART_COACH_NOTE));

        exercise_add_empty_list_btn(list);
    } else if(EXERCISE_SETTING_ITEM_TYPE_GOALS == item_type) {
        printf("%s: goals\n", __FUNCTION__);

        item_info.prim_txt_id = WATCH_TEXT_ID_GOALS;
        exercise_add_list_btn(NULL, list, &item_info);

        if(0 != (sport_info.goals_item_bitmap & (1 << EXERCISE_VALUE_TYPE_DISTANCE))) {
            item_info.img_src = ICON_EXERCISE_DISTANCE;
            item_info.img_bg_color = LV_COLOR_GREEN;
            item_info.prim_txt_id = WATCH_TEXT_ID_DISTANCE;
            item_info.cb = exercise_goals_distance_cb;
            item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
            exercise_add_list_btn(NULL, list, &item_info);
        }

        if(0 != (sport_info.goals_item_bitmap & (1 << EXERCISE_VALUE_TYPE_TIME))) {
            item_info.img_src = ICON_EXERCISE_TIME;
            item_info.img_bg_color = LV_COLOR_MAKE(0x00, 0x80, 0x80);
            item_info.prim_txt_id = WATCH_TEXT_ID_TIME;
            item_info.cb = exercise_goals_time_cb;
            exercise_add_list_btn(NULL, list, &item_info);
        }

        if(0 != (sport_info.goals_item_bitmap & (1 << EXERCISE_VALUE_TYPE_CALORIE))) {
            item_info.img_src = ICON_EXERCISE_CALORIE;
            item_info.img_bg_color = LV_COLOR_MAKE(0xB0, 0x00, 0x00);
            item_info.prim_txt_id = WATCH_TEXT_ID_CALORIE;
            item_info.cb = exercise_goals_calorie_cb;
            exercise_add_list_btn(NULL, list, &item_info);
        }

        item_info.img_src = ICON_EXERCISE_NO_GOALS;
        item_info.img_bg_color = LV_COLOR_MAKE(0x27, 0xAD, 0xFE);
        item_info.prim_txt_id = WATCH_TEXT_ID_GOALLESSNESS;
        item_info.cb = exercise_goals_goallessness_cb;
        item_info.type = EXERCISE_LIST_BTN_TYPE_NORMAL;
        exercise_add_list_btn(NULL, list, &item_info);
    } else if(EXERCISE_SETTING_ITEM_TYPE_REMINDER == item_type) {
        printf("%s: reminder\n", __FUNCTION__);

        exercise_nv_reminder_t * nv
            = (exercise_nv_reminder_t *)exercise_get_nv_pointer(item->data,
                                                                sport_info.setting_item_bitmap,
                                                                item_type);

        item_info.prim_txt_id = WATCH_TEXT_ID_REMINDER;
        exercise_add_list_btn(NULL, list, &item_info);

        for(uint8_t i = 0;i < EXERCISE_MAX_REMINDER_ITEM_NUM;i++) {
            if(0 != (sport_info.reminder_item_bitmap & (1 << i))) {
                switch(i) {
                case EXERCISE_REMINDER_ITEM_TYPE_INTERVAL:
                    item_info.img_src = ICON_EXERCISE_INTERVAL_REMINDER;
                    item_info.img_bg_color = LV_COLOR_ORANGE;
                    item_info.prim_txt_id = WATCH_TEXT_ID_INTERVAL_REMINDER;
                    exercise_get_interval_reminder_txt(nv, item_info.sec_txt, EXERCISE_MAX_TXT_LEN);
                    item_info.cb = exercise_interval_reminder_cb;
                    item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
                    exercise_add_list_btn(item, list, &item_info);
                    break;
                case EXERCISE_REMINDER_ITEM_TYPE_HEART_RATE:
                    item_info.img_src = ICON_EXERCISE_HEART_RATE;
                    item_info.img_bg_color = LV_COLOR_MAKE(0xB0, 0x00, 0x00);
                    item_info.prim_txt_id = WATCH_TEXT_ID_HEART_RATE_REMINDER;
                    exercise_get_hr_reminder_txt(nv, item_info.sec_txt, EXERCISE_MAX_TXT_LEN);
                    item_info.cb = exercise_hr_reminder_cb;
                    item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
                    exercise_add_list_btn(item, list, &item_info);
                    break;
                case EXERCISE_REMINDER_ITEM_TYPE_TRAINING_EFFECT:
                    item_info.img_src = ICON_EXERCISE_TRAINING_EFFECT;
                    item_info.img_bg_color = LV_COLOR_MAKE(0xC0, 0x60, 0x00);
                    item_info.prim_txt_id = WATCH_TEXT_ID_TRAINING_EFFECT;
                    item_info.sec_txt[0] = '\0';
                    item_info.cb = NULL;
                    item_info.setting_cb = exercise_training_effect_cb;
                    item_info.type = EXERCISE_LIST_BTN_TYPE_SWITCH;
                    if(0 == nv->is_training_effect_on) {
                        item_info.is_on = false;
                    } else {
                        item_info.is_on = true;
                    }
                    exercise_add_list_btn(NULL, list, &item_info);
                    break;
                case EXERCISE_REMINDER_ITEM_TYPE_PHYSICAL_STATE:
                    item_info.img_src = ICON_EXERCISE_BODY_STATE;
                    item_info.img_bg_color = LV_COLOR_GREEN;
                    item_info.prim_txt_id = WATCH_TEXT_ID_PHYSICAL_STATE;
                    item_info.sec_txt[0] = '\0';
                    item_info.cb = NULL;
                    item_info.setting_cb = exercise_physical_state_cb;
                    item_info.type = EXERCISE_LIST_BTN_TYPE_SWITCH;
                    if(0 == nv->is_physical_state_on) {
                        item_info.is_on = false;
                    } else {
                        item_info.is_on = true;
                    }
                    exercise_add_list_btn(NULL, list, &item_info);
                    break;
                }
            }
        }
    } else if(EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP == item_type) {
        printf("%s: Intelligent running companion\n", __FUNCTION__);

        exercise_nv_intel_run_comp_t * nv
            = (exercise_nv_intel_run_comp_t *)exercise_get_nv_pointer(item->data,
                                                                      sport_info.setting_item_bitmap,
                                                                      item_type);

        item_info.prim_txt_id = WATCH_TEXT_ID_INTEL_RUN_COMP;
        exercise_add_list_btn(NULL, list, &item_info);

        item_info.img_src = ICON_EXERCISE_RUN_WITH_AI;
        item_info.img_bg_color = LV_COLOR_MAKE(0x00, 0xA0, 0xA0);
        item_info.type = EXERCISE_LIST_BTN_TYPE_SWITCH;
        if(0 == nv->is_on) {
            item_info.is_on = false;
        } else {
            item_info.is_on = true;
        }
        item_info.setting_cb = exercise_intel_run_comp_sw_cb;
        exercise_add_list_btn(NULL, list, &item_info);

        item_info.img_src = ICON_EXERCISE_PACE;
        item_info.img_bg_color = LV_COLOR_ORANGE;
        item_info.prim_txt_id = WATCH_TEXT_ID_GOAL_PACE;
        if(LV_BIDI_DIR_RTL == lv_obj_get_base_dir(obj)) {
            snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "\"%02d'%d/%s", nv->value % 60,
                     nv->value / 60, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
        } else {
            snprintf(item_info.sec_txt, EXERCISE_MAX_TXT_LEN, "%d'%02d\"/%s", nv->value / 60,
                     nv->value % 60, (char *)lv_lang_get_text(WATCH_TEXT_ID_KILOMETRE));
        }
        item_info.cb = exercise_goal_pace_cb;
        item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
        btn = exercise_add_list_btn(item, list, &item_info);
        if(0 == nv->is_on) {
            lv_obj_set_style_local_text_color(lv_obj_get_child_back(btn, NULL), LV_LABEL_PART_MAIN,
                                              LV_STATE_DEFAULT, LV_COLOR_GRAY);
        }
    }

    exercise_add_empty_list_btn(list);

    return(obj);
}

static void exercise_wakey_sw_cb(lv_obj_t * sw, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    bool state = lv_switch_get_state(sw);
    printf("%s: new state %d\n", __FUNCTION__, state);

    exercise_setting_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING);
    exercise_sport_info_t sport_info;
    exercise_get_sport_info(ext->item->type, &sport_info);
    uint32_t offset = exercise_get_nv_offset(sport_info.setting_item_bitmap,
                                             EXERCISE_SETTING_ITEM_TYPE_WAKEY);
    exercise_nv_wakey_t * nv = (exercise_nv_wakey_t *)(&ext->item->data[offset]);
    if(state) {
        nv->is_wakey = 1;
    } else {
        nv->is_wakey = 0;
    }
    exercise_write_nv(ext->item->type, offset, sizeof(exercise_nv_wakey_t), nv,
                      EXERCISE_WRITE_NV_MODE_REPLACE);
}

static void exercise_setting_btn_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_setting_1_create(btn);
}

static void exercise_add_setting_btn(lv_obj_t * list, exercise_item_t * item,
                                     uint8_t setting_item_bitmap)
{
    exercise_item_info_t item_info = {};
    char * txt;
    lv_obj_t * btn;
    for(uint8_t i = 0;i < EXERCISE_MAX_SETTING_ITEM_NUM;i++) {
        if(0 == (setting_item_bitmap & (1 << i))) continue;

        if(EXERCISE_SETTING_ITEM_TYPE_SMART_COACH == i) {
            item_info.img_src = ICON_EXERCISE_SMART_COACH;
            item_info.img_bg_color = LV_COLOR_MAKE(0x00, 0xA0, 0xA0);
            item_info.prim_txt_id = WATCH_TEXT_ID_SMART_COACH;
            exercise_nv_smart_coach_t * nv
                = (exercise_nv_smart_coach_t*)exercise_get_nv_pointer(item->data,
                                                                      setting_item_bitmap, i);
            if(1 == nv->is_smart_coach_on) {
                txt = (char *)lv_lang_get_text(WATCH_TEXT_ID_ON);
            } else {
                txt = (char *)lv_lang_get_text(WATCH_TEXT_ID_OFF);
            }
            Hal_Mem_Copy(item_info.sec_txt, txt, strlen(txt) + 1);
            item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
            item_info.cb = exercise_setting_btn_cb;
        } else if(EXERCISE_SETTING_ITEM_TYPE_GOALS == i) {
            item_info.img_src = ICON_EXERCISE_GOALS;
            item_info.img_bg_color = LV_COLOR_MAKE(0x27, 0xAD, 0xFE);
            item_info.prim_txt_id = WATCH_TEXT_ID_GOALS;
            exercise_nv_goals_t * nv
                = (exercise_nv_goals_t *)exercise_get_nv_pointer(item->data,
                                                                 setting_item_bitmap, i);
            exercise_get_goals_txt(nv, item_info.sec_txt, EXERCISE_MAX_TXT_LEN);
            item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
            item_info.cb = exercise_setting_btn_cb;
        } else if(EXERCISE_SETTING_ITEM_TYPE_REMINDER == i) {
            item_info.img_src = ICON_EXERCISE_REMINDER;
            item_info.img_bg_color = LV_COLOR_MAKE(0xC0, 0x60, 0x00);
            item_info.prim_txt_id = WATCH_TEXT_ID_REMINDER;
            item_info.sec_txt[0] = '\0';
            item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
            item_info.cb = exercise_setting_btn_cb;
        } else if(EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP == i) {
            item_info.img_src = ICON_EXERCISE_RUN_WITH_AI;
            item_info.img_bg_color = LV_COLOR_MAKE(0x00, 0xA0, 0xA0);
            item_info.prim_txt_id = WATCH_TEXT_ID_INTEL_RUN_COMP;
            exercise_nv_intel_run_comp_t * nv
                = (exercise_nv_intel_run_comp_t*)exercise_get_nv_pointer(item->data,
                                                                         setting_item_bitmap, i);
            exercise_get_intel_run_comp_txt(nv, lv_obj_get_base_dir(list),
                                            item_info.sec_txt, EXERCISE_MAX_TXT_LEN);
            item_info.type = EXERCISE_LIST_BTN_TYPE_NEXT_MENU;
            item_info.cb = exercise_setting_btn_cb;
        } else if(EXERCISE_SETTING_ITEM_TYPE_WAKEY == i) {
            item_info.img_src = ICON_EXERCISE_WAKEY;
            item_info.img_bg_color = LV_COLOR_ORANGE;
            item_info.prim_txt_id = WATCH_TEXT_ID_WAKEY;
            item_info.sec_txt[0] = '\0';
            item_info.type = EXERCISE_LIST_BTN_TYPE_SWITCH;
            item_info.cb = NULL;
            item_info.setting_cb = exercise_wakey_sw_cb;
            exercise_nv_wakey_t * nv
                = (exercise_nv_wakey_t *)exercise_get_nv_pointer(item->data,
                                                                 setting_item_bitmap, i);
            if(nv->is_wakey) {
                item_info.is_on = true;
            } else {
                item_info.is_on = false;
            }
        }
        btn = exercise_add_list_btn(item, list, &item_info);
        lv_watch_set_free_num(btn, i);
    }
}

static void exercise_remove_sport_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) {
        return;
    }

    printf("%s\n", __FUNCTION__);

    exercise_setting_ext_t * setting_ext = exercise_get_ext(ACT_ID_EXERCISE_SETTING);
    char path[30] = {};
    snprintf(path, 30, "%s%d%s", EXERCISE_NV_PATH_PREFIX, setting_ext->item->type,
             EXERCISE_NV_PATH_SUFFIX);
    lv_fs_remove(path);

    exercise_ext_t * ext = exercise_get_ext(ACT_ID_EXERCISE);
    _lv_ll_remove(&ext->items, setting_ext->item);
    Hal_Mem_Free(setting_ext->item);

    exercise_nv_main_t nv = {};
    uint32_t data_size;
    exercise_read_nv(EXERCISE_TYPE_NONE, (uint8_t **)(&nv), &data_size);
    Hal_Mem_Set(nv.item, 0, EXERCISE_TYPE_MAX);
    exercise_item_t * item = _lv_ll_get_head(&ext->items);
    uint32_t i = 0;
    while(item) {
        nv.item[i++] = item->type;
        item = _lv_ll_get_next(&ext->items, item);
    }
    exercise_write_nv(EXERCISE_TYPE_NONE, 0, sizeof(exercise_nv_main_t), &nv,
                      EXERCISE_WRITE_NV_MODE_RESET);
    lv_obj_del(setting_ext->par_btn);
    exercise_del_ui(ACT_ID_EXERCISE_SETTING);
}

static lv_obj_t * exercise_setting_create(lv_obj_t * list_btn, exercise_item_t * item)
{
    lv_watch_activity_ext_t activity_ext = {};
    activity_ext.actId = ACT_ID_EXERCISE_SETTING;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = exercise_com_prepare_destroy;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    exercise_setting_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(exercise_setting_ext_t));
    LV_ASSERT_MEM(ext);

    ext->par_btn = list_btn;
    ext->item = item;

    lv_obj_t * list = exercise_add_list_ui(obj, 4);

    exercise_sport_info_t sport_info;
    exercise_get_sport_info(item->type, &sport_info);

    exercise_item_info_t item_info = {};
    item_info.prim_txt_id = WATCH_TEXT_ID_SETTINGS;
    char * txt = (char *)lv_lang_get_text(sport_info.txt_id);
    Hal_Mem_Copy(item_info.sec_txt, txt, strlen(txt) + 1);
    exercise_add_list_btn(item, list, &item_info);

    exercise_add_setting_btn(list, item, sport_info.setting_item_bitmap);

    item_info.img_src = ICON_EXERCISE_REMOVE_SPORT;
    item_info.img_bg_color = LV_COLOR_MAKE(0xB0, 0x00, 0x00);
    item_info.prim_txt_id = WATCH_TEXT_ID_REMOVE_SPORT;
    item_info.sec_txt[0] = '\0';
    item_info.cb = exercise_remove_sport_cb;
    item_info.type = EXERCISE_LIST_BTN_TYPE_NORMAL;
    exercise_add_list_btn(item, list, &item_info);

    exercise_add_empty_list_btn(list);

    return(obj);
}

/**********************
 *   GLOBAL FUNCTIONS
 ***********************/
 void exercise_main_sport_setting_cb(lv_obj_t * btn, lv_event_t event)
{
    if(LV_EVENT_CLICKED != event) return;

    printf("%s\n", __FUNCTION__);

    lv_obj_t * par_btn = lv_obj_get_parent(btn);
    lv_obj_t * label = lv_obj_get_child_back(par_btn, NULL);
    exercise_item_t * item = lv_watch_get_free_ptr(label);
    if(NULL == item) {
        printf("%s: warning, item is NULL\n", __FUNCTION__);
        return;
    }
    exercise_setting_create(par_btn, item);
}

uint32_t exercise_nv_init(uint8_t setting_item_bitmap, uint8_t ** data)
{
    if(NULL == data) {
        printf("%s: warning, data is NULL\n", __FUNCTION__);
        return(0);
    }

    uint32_t size = 0;
    uint32_t reminder_offset = 0;
    uint32_t run_offset = 0;
    for(uint8_t i = 0;i < EXERCISE_MAX_SETTING_ITEM_NUM;i++) {
        if(0 != (setting_item_bitmap & (1 << i))) {
            switch(i) {
            case EXERCISE_SETTING_ITEM_TYPE_SMART_COACH:
                size += sizeof(exercise_nv_smart_coach_t);
                break;
            case EXERCISE_SETTING_ITEM_TYPE_GOALS:
                size += sizeof(exercise_nv_goals_t);
                break;
            case EXERCISE_SETTING_ITEM_TYPE_REMINDER:
                reminder_offset = size;
                size += sizeof(exercise_nv_reminder_t);
                break;
            case EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP:
                run_offset = size;
                size += sizeof(exercise_nv_intel_run_comp_t);
                break;
            case EXERCISE_SETTING_ITEM_TYPE_WAKEY:
                size += sizeof(exercise_nv_wakey_t);
                break;
            }
        }
    }

    uint8_t * nv = Hal_Mem_Alloc(size);;
    Hal_Mem_Set(nv, 0, size);
    if(0 != (setting_item_bitmap & (1 << EXERCISE_SETTING_ITEM_TYPE_REMINDER))) {
        exercise_nv_reminder_t * reminder_nv = (exercise_nv_reminder_t *)(&nv[reminder_offset]);
        reminder_nv->hr.range_type = EXERCISE_HR_RANE_TYPE_HEART_LUNG_IMPROVE;
        reminder_nv->hr.min_value = EXERCISE_HEART_LUNG_IMPROVE_HR_LOW_VALUE;
        reminder_nv->hr.max_value = EXERCISE_HEART_LUNG_IMPROVE_HR_HIGH_VALUE;
    }
    if(0 != (setting_item_bitmap & (1 << EXERCISE_SETTING_ITEM_TYPE_INTEL_RUN_COMP))) {
        exercise_nv_intel_run_comp_t * run_nv = (exercise_nv_intel_run_comp_t *)(&nv[run_offset]);
        run_nv->type = EXERCISE_GOAL_PACE_TYPE_GENERAL_LEVEL;
        run_nv->value = EXERCISE_GOAL_PACE_GENERAL_TIME_VALUE;
    }
    *data = nv;
    return(size);
}

#endif /*USE_LV_WATCH_EXERCISE*/
